import { color } from '../../core/color.js';
import { logger } from '../../core/logger.js';

class Command {
  _name;
  _description;
  _handler;
  _options = [];
  _commands = [];
  _version;
  _positionalArgs;
  _allowSpacesInOptions = true;
  _parent;
  constructor(name) {
    this._name = name;
    this._handler = () => {
    };
  }
  /**
   * Add a subcommand to the current command.
   *
   * @param {Command} command - Command object to be added as a subcommand.
   * @returns {Command} - Returns the current Command object for chaining.
   */
  addCommand(command) {
    this._commands.push(command);
    command._parent = this;
    return this;
  }
  /**
   * Enable or disable spaces in options.
   *
   * @param {boolean} allow - Boolean to allow or disallow spaces in option values.
   * @returns {Command} - Returns the current Command object for chaining.
   */
  allowSpacesInOptions(allow) {
    this._allowSpacesInOptions = allow;
    return this;
  }
  /**
   * Set the description for the command.
   *
   * @param {string} desc - Description string.
   * @returns {Command} - Returns the current Command object for chaining.
   */
  description(desc) {
    this._description = desc;
    return this;
  }
  /**
   * Gets the children commands of the current command.
   *
   * @returns {Command[]} - Get the children commands of the current command.
   */
  getChildCommands() {
    return this._commands;
  }
  /**
   * Set the value for positionalArgs.
   *
   * @param {boolean} positionalArg - positionalArgs boolean.
   * @returns {Command} - Returns the current Command object for chaining.
   */
  positionalArgs(positionalArg) {
    this._positionalArgs = positionalArg;
    return this;
  }
  /**
   * Gets the parent command.
   *
   * @returns {Command} - Returns the parent command.
   */
  getParentCommand() {
    return this._parent;
  }
  /**
   * Returns the name of the current command.
   *
   * @returns {string} - Returns the name of the command.
   */
  getName() {
    return this._name;
  }
  /**
   * Returns the description of the current command.
   *
   * @returns {string} - Returns the description of the current command.
   */
  getDescription() {
    return this._description;
  }
  /**
   * Returns the options of the current command.
   *
   * @returns {Option[]} - Returns the options of the current command.
   */
  getOptions() {
    return this._options;
  }
  /**
   * Add an option for the command.
   *
   * @param {string} alias - Option alias (short form).
   * @param {string} name - Option name (long form).
   * @param {string} description - Option description.
   * @returns {Command} - Returns the current Command object for chaining.
   */
  option(alias, name, description) {
    this._options.push({ alias, name, description });
    return this;
  }
  /**
   * Assign a handler function for the command.
   *
   * @param {(args: string[], options: Record<string, unknown>) => void} fn - Function to be executed when the command is called.
   * @returns {Command} - Returns the current Command object for chaining.
   */
  handler(fn) {
    this._handler = fn;
    return this;
  }
  /**
   * Parse the command line arguments and process the command.
   */
  parse() {
    this.processSubCommand(this, process.argv.slice(2));
  }
  /**
   * Assign a version string to the command and adds an option to display the version.
   *
   * @param {string} versionString - Version string.
   * @returns {Command} - Returns the current Command object for chaining.
   */
  version(versionString) {
    this._version = versionString;
    this.option("-v", "--version", "Display the current version");
    return this;
  }
  showHelp() {
    console.log(color.blue(`
 Command: ${this._name}`));
    console.log(` Description: ${this._description}`);
    if (this._options.length > 0) {
      logger.log(color.green(` Options:`));
      this._options.forEach((opt) => {
        logger.log(
          `${color.white(
            `   ${color.green(`${opt.alias}`)}${color.white(",")} ${color.green(`${opt.name}`)}: ${opt.description}`
          )}`
        );
      });
      logger.log(`
`);
    }
    if (this._commands.length > 0) {
      logger.log(color.red(` Subcommands:`));
      this._commands.forEach((cmd) => {
        logger.log(`${color.white(`   ${cmd._name}: ${cmd._description}`)}`);
      });
      logger.log(`
`);
    }
  }
  /**
   * Parses the options from the provided arguments array.
   *
   * @param {string[]} args - The arguments array.
   * @returns {Record<string, unknown>} - Returns an object containing parsed options.
   */
  parseOptions(args) {
    const options = {};
    let i = 0;
    while (i < args.length) {
      const arg = args[i];
      if (arg.startsWith("--")) {
        const option = this._options.find((opt) => opt.name === arg);
        if (option) {
          if (this._allowSpacesInOptions && i + 1 < args.length && !args[i + 1].startsWith("-")) {
            let value = args[i + 1];
            i += 2;
            while (i < args.length && !args[i].startsWith("-")) {
              value += ` ${args[i]}`;
              i++;
            }
            options[arg.slice(2)] = value;
          } else {
            options[arg.slice(2)] = true;
            i++;
          }
        } else {
          i++;
        }
      } else if (arg.startsWith("-")) {
        const option = this._options.find((opt) => opt.alias === arg);
        if (option) {
          if (this._allowSpacesInOptions && i + 1 < args.length && !args[i + 1].startsWith("-")) {
            let value = args[i + 1];
            i += 2;
            while (i < args.length && !args[i].startsWith("-")) {
              value += ` ${args[i]}`;
              i++;
            }
            options[option.name.slice(2)] = value;
          } else {
            options[option.name.slice(2)] = true;
            i++;
          }
        } else {
          i++;
        }
      } else {
        i++;
      }
    }
    return options;
  }
  async processSubCommand(command, args) {
    if (args.length === 0 && !command._handler) {
      return;
    }
    const positionalArgs = [];
    let optionsArgsStart = args.length;
    for (let i = 0; i < args.length; i++) {
      const arg = args[i];
      if (arg.startsWith("-")) {
        optionsArgsStart = i;
        break;
      }
      if (arg.startsWith("arg:")) {
        positionalArgs.push(arg.slice(4));
        continue;
      }
      const subCommand = command._commands.find((cmd) => cmd._name === arg);
      if (subCommand) {
        const { positionalArgs: subPosArgs, optionsArgs: subOptArgs } = this.splitArgs(args.slice(i + 1));
        this.processSubCommand(subCommand, [...subPosArgs, ...subOptArgs]);
        return;
      }
      positionalArgs.push(arg);
      if (!command._positionalArgs) {
        logger.log("\n");
        logger.error(color.red(`The command "${arg}" does not exist.`));
        logger.info(`Try ${color.bold(color.blue("robo --help"))} to see all available commands.`);
        logger.log("\n");
        return;
      }
    }
    const optionsArgs = args.slice(optionsArgsStart);
    const parsedOptions = command.parseOptions(optionsArgs);
    if (parsedOptions.help) {
      command.showHelp();
      return;
    }
    if (command._commands.length && command._version && (args.includes("-v") || args.includes("--version"))) {
      console.log(command._version);
      process.exit(0);
    }
    await command._handler(positionalArgs, parsedOptions);
  }
  splitArgs(args) {
    const positionalArgs = [];
    let optionsArgsStart = args.length;
    for (let i = 0; i < args.length; i++) {
      const arg = args[i];
      if (arg.startsWith("-")) {
        optionsArgsStart = i;
        break;
      }
      positionalArgs.push(arg);
    }
    const optionsArgs = args.slice(optionsArgsStart);
    return { positionalArgs, optionsArgs };
  }
}

export { Command };
